﻿using System;
using System.IO;
using System.Collections.Generic;
using ASquared.SymbolicMath;
using ASquared.ModelStatistics;

namespace ASquared.ModelOptimization
{
    public class LinearModeler
    {
        private String _commentStart;
        private Char[] _delims = new Char[] { ',' };
        private LinearModel _linModel;
        private LinearMapper _mapper;
        private Dictionary<String, Int32> _varNamesToCols = new Dictionary<String, Int32>();
        private Matrix _X, _Y;
        private SymbolicMatrix _SymX, _SymY;
        private Double _currEval; 

        public LinearModeler(String WeightsFile, String DataFile, LinearMapper mapper, String CommentStart = "#")
        {
            _commentStart = CommentStart;
            _mapper = mapper;
            _linModel = LinearModel.FromFile(WeightsFile);
            ReadData(DataFile);
        }

        // Properties 
        public Matrix X
        {
            get
            {
                return _X;
            }
        }

        public Matrix Y
        {
            get
            {
                return _Y;
            }
        }

        public Double CurrEval
        {
            get
            {
                return _currEval; 
            }
        }

        public SymbolicMatrix CurrSymX
        {
            get
            {
                return _SymX.GetRow(_mapper.SampleIndex);
            }
        }

        public SymbolicMatrix CurrSymY
        {
            get
            {
                return _SymY;
            }
        }

        public LinearMapper Mapper
        {
            get
            {
                return _mapper; 
            }
        }

        public LinearModel Model
        {
            get
            {
                return _linModel;
            }
        }

        // Methods 
        private void ReadData(String DataFile)
        {
            StreamReader sr = new StreamReader(DataFile);
            GetNames(sr);
            GetData(sr);
            sr.Close();
        }

        private String[] ReadLine(StreamReader sr)
        {
            String line;
            do
            {
                line = sr.ReadLine().Trim();
            } while (line == "" || line.StartsWith(_commentStart));
            return line.Split(_delims, StringSplitOptions.None);
        }

        private void GetNames(StreamReader sr)
        {
            List<String> varNames = new List<String>(_linModel.VariableNames);
            String[] headers = ReadLine(sr);
            for (Int32 i = 0; i < headers.Length; i++)
                if (varNames.Contains(headers[i]))
                    _varNamesToCols.Add(headers[i], i);
        }

        private void GetData(StreamReader sr)
        {
            Double[,] rowvals = new Double[1, _varNamesToCols.Count];
            Int32[] cols = new Int32[_varNamesToCols.Count];
            _varNamesToCols.Values.CopyTo(cols, 0);
            Int32 maxCol = Convert.ToInt32(Statistics.Max(Array.ConvertAll(cols, c => (Double)c)));
            Matrix data = new Matrix(1, _varNamesToCols.Count, 0.0);
            while (!sr.EndOfStream)
            {
                // Split the line into an array
                String[] line = ReadLine(sr);
                if (line.Length <= maxCol)
                    continue;

                // Get only the data in the columns 
                for (int i = 0; i < cols.Length; i++)
                    rowvals[0, i] = Convert.ToDouble(line[cols[i]]);

                // Add the row to the Matrix
                data.AppendRows(rowvals);
            }
            data.RemoveRowAt(0);
            _X = new Matrix(data.NumOfRows, 1, 1.0);
            _X.AppendCols(data);
            _SymX = _X; 
            EvaluateAll();
        }

        private void EvaluateAll()
        {
            _Y = _linModel.Evaluate(_X, false);
        }

        public Double EvaluateCurrent(Int32 targetIndex)
        {
            return _currEval = GetCurrent(targetIndex, false).Eval(_mapper.Variables, _mapper.Values); 
        }

        public Symbol GetCurrent(Int32 targetIndex, bool useMapperSymbols)
        {
            _SymX = _X;
            Int32 col;
            String[] varNames = _mapper.Variables;
            Symbol[] symbols = _mapper.Symbols;
            List<String> vars = new List<String>(_varNamesToCols.Keys);
            for (int i = 0; i < varNames.Length; i++)
                if ((col = vars.IndexOf(varNames[i])) != -1)
                    _SymX[_mapper.SampleIndex, col + 1] = useMapperSymbols ? symbols[i] : (Symbol)varNames[i];
            _SymY = _linModel.Substitute(_SymX.GetRow(_mapper.SampleIndex), false); 
            return _SymY[targetIndex]; 
        }
    }
}
